package modele;

import mesExceptions.Impossible;

import java.text.SimpleDateFormat;
import java.util.Properties;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.Set;
import java.util.TreeSet;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Date;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;

import java.rmi.server.UnicastRemoteObject;

import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

import com.healthmarketscience.rmiio.RemoteInputStream;
import com.healthmarketscience.rmiio.RemoteInputStreamClient;
import com.healthmarketscience.rmiio.RemoteInputStreamServer;
import com.healthmarketscience.rmiio.GZIPRemoteInputStream;

import com.sun.mail.smtp.SMTPTransport;

import au.com.bytecode.opencsv.CSVReader;

public class BdImpl extends UnicastRemoteObject implements Bd {
	/**
	 * 
	 */
	private static final long serialVersionUID = 6L;

	public static String DATADIR="Donnees";
	public static String CSVDIR=DATADIR+"/csv/";
	public static String BACKUPDIR=DATADIR+"/backup/"; 
	public static String DEFAULTBACKUPFILE="bd.tmp";

	SortedSet<UniteEnseignement> ues;
	SortedSet<Dossier> dossiers; 
	SortedSet<Parcours> parcours;

	public BdImpl() throws Exception {
		dossiers = new TreeSet<Dossier>(new ComparatorDossier());
		parcours = new TreeSet<Parcours>(new ComparatorParcours());
		ues = new TreeSet<UniteEnseignement>(new ComparatorUE());
		importeListeUEs();
		importeListeParcours();
		importeListeContenuParcours();
	}

	/**
	 *methode pour charger la liste des UEs
	 **/
	private void importeListeUEs() throws Exception {
		CSVReader reader = new CSVReader(new BufferedReader(new InputStreamReader(new FileInputStream(CSVDIR+"listeUEs.csv"),"ISO-8859-1")), ';');
		List<String[]> listeUE = reader.readAll();
		boolean entete=false;
		for (String[] uneUE : listeUE) {
			//je ne veux pas de la première ligne = entête
			if(!entete) {
				entete=true;
				continue;
			}
			UniteEnseignement ue =  new UniteEnseignementImpl(uneUE);
			ues.add(ue);
		}
		reader.close();
	}

	/**
	 *methode pour charger la liste des parcours
	 **/
	private void importeListeParcours() throws Exception {
		CSVReader reader = new CSVReader(new BufferedReader(new InputStreamReader(new FileInputStream(CSVDIR+"listeParcours.csv"),"ISO-8859-1")), ';');
		List<String[]> listeParcours = reader.readAll();
		boolean entete=false;
		for (String[] unParcours : listeParcours) {
			//je ne veux pas de la première ligne = entête
			if(!entete) {
				entete=true;
				continue;
			}
			Parcours par = new ParcoursImpl(this, unParcours);
			add(par);
		}
		reader.close();
	}

	/**
	 *methode pour definir le contenu des parcours
	 **/
	private void importeListeContenuParcours() throws Exception {
		CSVReader reader = new CSVReader(new BufferedReader(new InputStreamReader(new FileInputStream(CSVDIR+"listeContenuParcours.csv"),"ISO-8859-1")), ';');
		List<String[]> listeContenuParcours = reader.readAll();
		boolean entete=false;
		for (String[] unContenu : listeContenuParcours) {
			//je ne veux pas de la première ligne = entête
			if(!entete) {
				entete=true;
				continue;
			}
			Parcours par = getParcours(unContenu[0]);
			if (par==null)
				System.err.println("Parcours non reconnu : "+unContenu[0]);
			else {
				//UE obligatoires;
				StringTokenizer st =  new StringTokenizer(unContenu[1],",");
				while (st.hasMoreTokens()){
					String ues = st.nextToken();
					UniteEnseignement ue=getUniteEnseignement(ues);
					if (ue!=null)
						addObligatoire(par,ue);
					else
						System.err.println("UE non reconnue : "+ues);
				}

				if(unContenu.length>2) {
					//UE Choix 1
					st =  new StringTokenizer(unContenu[2],",");
					while (st.hasMoreTokens()){
						String ues = st.nextToken();
						UniteEnseignement ue=getUniteEnseignement(ues);
						if (ue!=null)
							addChoix1(par,ue);
						else
							System.err.println("UE non reconnue : "+ues);
					}

					if(unContenu.length>3) {
						//UE Choix 2
						st =  new StringTokenizer(unContenu[3],",");
						while (st.hasMoreTokens()){
							String ues = st.nextToken();
							UniteEnseignement ue=getUniteEnseignement(ues);
							if (ue!=null)
								addChoix2(par,ue);
							else
								System.err.println("UE non reconnue : "+ues);
						}
					}
				}
			}
		}
	reader.close();
	}

	/**
	 *methode pour importer les candidats depuis Apoflux
	 **/
	private int importApoflux(InputStreamReader str)  throws Exception {
		synchronized(dossiers) {
			CSVReader reader = new CSVReader(new BufferedReader(str), ';');
			List<String[]> apoflux = reader.readAll();
			int new_imports=0;
			Map<String,String> fields=new HashMap<String,String>();
			boolean entete=false;
			String[] headers = apoflux.get(0);
			int nb=1;
			for(String[] line : apoflux) {
				//je ne veux pas de la première ligne = entête
				if(!entete) {
					entete=true;
					continue;
				}
				for(int i=0;i<headers.length;i++) {
					fields.put(headers[i], line[i]);
				}
				//Je veux les M2 info mais pas les M2 CSI
				if ((fields.get("COD_ETP").startsWith("MPIN9"))||(fields.get("COD_ETP").startsWith("MRIN9"))) {
					if(!fields.get("COD_ETP").startsWith("MPIN92_110")&&!fields.get("COD_ETP").startsWith("MRIN92_110")) {
						if (fields.get("DOSSIER_ANNULE").contains("N")) {
							// ne pas reimporter si deja un dossier avec no apoflux ou nom+prenom identique
							if (getDossier(fields.get("NO_DOSSIER"))==null){
								Dossier dos = new DossierImpl(this,fields);
								dossiers.add(dos);
								new_imports++;
							}
						}					
						else
							System.out.println("Dossier annule: " + fields.get("NOM") + ": "+ fields.get("DOSSIER_ANNULE"));
					}
				}
			}
			
			//détection des voeux uniquement pour CSI (utile pour les stats)
			for(String[] line : apoflux) {
				//je ne veux pas de la première ligne = entête
				if(!entete) {
					entete=true;
					continue;
				}
				for(int i=0;i<headers.length;i++) {
					fields.put(headers[i], line[i]);
				}
				if(fields.get("COD_ETP").startsWith("MPIN92_110")&&!fields.get("COD_ETP").startsWith("MRIN92_110")) { //M2 CSI
					if (fields.get("DOSSIER_ANNULE").contains("N")) {
						if (getDossier(fields.get("NO_DOSSIER"))==null){
							//TODO: il faudra stocker ce chiffre dans la base pour le faire afficher sur les stats
							System.out.println("Voeu uniquement pour CSI (" + nb++ +") : "+fields.get("NO_DOSSIER"));
						}
					}
				}
			}
			reader.close();
			return new_imports;
		}
	}

	public int importeListeApoflux(String filename) throws Exception {
		return importApoflux(new InputStreamReader(new FileInputStream(filename),"ISO-8859-1"));

	}
	public int importeListeApoflux(RemoteInputStream inFile) throws Exception {
		return importApoflux(new InputStreamReader(RemoteInputStreamClient.wrap(inFile),"ISO-8859-1"));		
	}
	
	public Parcours getParcours(int ident) throws Exception {
		for (Parcours par : parcours) {
			if (par.getIdent()==ident)
				return par;
		}
		return null;
	}

	private Parcours getParcours(String acro) throws Exception {
		for (Parcours par : parcours) {
			if (par.getSigle().equals(acro))
				return par;
		}
		return null;
	}

	public SortedSet<Parcours> getParcours() throws Exception {
		return parcours;
	}

	public UniteEnseignement getUniteEnseignement(Integer ident)
	throws Exception {
		for (UniteEnseignement ue : ues) {
			if (ue.getIdent().equals(ident))
				return ue;
		}
		return null;
	}

	public UniteEnseignement getUniteEnseignement(String st)
	throws Exception {
		for (UniteEnseignement ue : ues) {
			if (ue.getSigle().equals(st))
				return ue;
		}
		return null;
	}

	public SortedSet<UniteEnseignement> getUniteEnseignements() throws Exception {
		return ues;
	}

	public SortedSet<Dossier> getDossiers() throws Exception {
		return dossiers;
	}

	public SortedSet<Dossier> getDossiersRetenus() throws Exception {
		SortedSet<Dossier> aux = new TreeSet<Dossier>(new ComparatorDossierNom());
		for (Dossier dos : dossiers) {
			if (dos.isRetenu())
				aux.add(dos);
		}
		return aux;
	}

	/** Creation d'un nouveau dossier
	 *  La sauvegarde sur disque est faite lors de l'appel a dos.record()
	 **/
//	public Dossier addDossier() throws Exception {
//		Dossier dos = new DossierImpl(this);
//		return dos;
//	}

	public void add(Dossier dos) throws Exception {
		dossiers.add(dos);
	}

	public void add(Parcours opt) throws Exception {
		parcours.add(opt);
	}

	public void add(UniteEnseignement ue) throws Exception {
		ues.add(ue);
	}

	public void remove(Dossier dos) throws Exception {
		dossiers.remove(dos);
	}

	public void remove(Parcours opt) throws Exception {
		parcours.remove(opt);
	}

	public void remove(UniteEnseignement ue) throws Exception {
		ues.remove(ue);
	}

	public void addObligatoire(Parcours opt, UniteEnseignement ue) throws Exception {
		opt.addObligatoire(ue);
		ue.addObligatoire(opt);
	}

	public void addChoix1(Parcours opt,UniteEnseignement ue) throws Exception {
		opt.addChoix1(ue);
		ue.addChoix1(opt);
	}

	public void addChoix2(Parcours opt, UniteEnseignement ue) throws Exception {
		opt.addChoix2(ue);
		ue.addChoix2(opt);
	}

	public void removeObligatoire(Parcours opt, UniteEnseignement ue) throws Exception {
		opt.removeObligatoire(ue);
		ue.removeObligatoire(opt);
	}

	public void removeChoix1(Parcours opt, UniteEnseignement ue) throws Exception {
		opt.removeChoix1(ue);
		ue.removeChoix1(opt);
	}

	public void removeChoix2(Parcours opt, UniteEnseignement ue) throws Exception {
		opt.removeChoix2(ue);
		ue.removeChoix2(opt);
	}

	// les algos
	/** Pour diminuer la taille de la base de donnees
	 *  l'historique des modifications des dossiers est limite a une taille
	 **/
//	public void restreindreHistorique(int taille) throws Exception {
//		for (Dossier dos : dossiers)
//			dos.restreindreHistorique(taille);
//	}

	/** Pour diminuer la taille de la base de donnees
	 *  les dossiers non traitables sont elimines
	 *  traitable = au moins une fiche dans la demande courante
	 *  la suppression d'un tel dossier ne doit pas laisser de reference a null
	 **/
//	public void restreindreTraitable() throws Exception {
//		for (Dossier dos : dossiers) {
//			if (!dos.isTraitable())
//				dossiers.remove(dos);
//		}
//	}

	private Dossier getDossier(String idApo) throws Exception {
		for (Dossier dos : dossiers) {
			if (dos.getEtudiant().getIdApoflux().equals(idApo))
				return dos;
		}
		return null;
	}


	private void affectation(Bd.Operation op) throws Exception {
		// on recalcule les demandes pour corriger un bug !
		updateDemandes();
		// on charge le fichier des Ues incompatibles
		//			setUesIncompatibles("Donnees/uesIncompatibles.txt");
		// pour ecraser le contenu du fichier des conflits entre ues.
		//			File file = new File("Donnees/ueConflitEdt.txt");
		//			file.delete();
		if (dossiers.size()>0){
			Date date = new Date();
			// tri des dossiers par merite et ajout nouvelle proposition
			SortedSet<Dossier> aux = new TreeSet<Dossier>(new ComparatorDossierMoyenne());
			for (Dossier dos : dossiers) {
				// affectation necessite nombreuses simulations
				if (op == Operation.AFFECTATION)
					// Premiere proposition
					// --> on efface toutes les propositions,
					dos.clearPropositions();
				else if (op == Operation.NOUVELLE_AFFECTATION)
					// Nouvelle proposition
					// --> on efface celle du jour (simulation) si elle existe
					dos.removeProposition(date);
				dos.addProposition(date);
				aux.add(dos);
			}
			// nettoyage des references dans les parcours
			for (Parcours par : parcours)
				par.clearPropositions();
			// nettoyage des references dans les Ues
			for (UniteEnseignement ue : ues)
				ue.clearPropositions();
			// les propositions precedentes non refusees sont "conservees"
			for (Dossier dos : aux) {
				System.out.println("Dossier : "+dos.getEtudiant().getIdApoflux());
				dos.conserverPropositionAcceptee();
				dos.affecterDossiers(true);
				dos.recordProposition();
			}
		}
		else
			throw new Impossible("INUTILE d'affecter les dossiers.\n Il n'y en a pas!");
	}

	//Modification d'un ou plusieurs dossiers
	public void OpDossier(String id, Operation op) throws Exception {
		Dossier dos=null;
		if(!id.isEmpty())
			dos = getDossier(id);
		switch(op) {
		case DELETE:	//suppression
			if (dos != null)
				remove(dos);
			else
				throw new Impossible("Pas de dossier ayant ce numero!");
			break;
		case REINIT_DOSSIER:
			if (dos != null){
				dos.getEvaluation().setMoyenneBac4(new Float(0));
				dos.getEvaluation().setMoyenneBac3(new Float(0));
				dos.getEvaluation().setNoteDossier(new Float(0));
				dos.getEvaluation().setNbBac4(new Integer(0));
				dos.getEvaluation().setNbBac3(new Integer(0));
			}
			else
				throw new Impossible("Pas de dossier ayant ce numero!");
			break;
		case RELANCE:
			if (dossiers.size()>0) {
				for (Dossier d : dossiers) {
					if (d.getReponseCoupon().equals("-")){
						String msg = "Bonjour\n"+
						"\n\tNumero de dossier : "+d.getEtudiant().getIdApoflux()+
						"\n\tNom : "+d.getEtudiant().getNom()+
						"\n\nSauf erreur de notre part, vous n'avez pas retourné le coupon réponse "+
						"au sujet de votre candidature au Master 2 Informatique de Bordeaux 1."+
						"\n\nMerci de nous renvoyer le coupon réponse le plus rapidement possible et"+
						"avant le 20 juillet afin de confirmer que vous acceptez notre proposition."+
						"\n\nCordialement,\n"+
						"\nPour le Master Informatique,\n"+
						"Le secrétariat du Master Informatique\n";
						sendmail(d.getEtudiant().getEmail(), msg, "Master 2 Informatique Bordeaux 1 : Relance", "");
					}
				}
			}
			else
				throw new Impossible("INUTILE d'imprimer les courriers.\n Il n'y en a pas!");
			break;
		case AFFECTATION:
			affectation(Operation.AFFECTATION);
			break;
		case NOUVELLE_AFFECTATION:
			affectation(Operation.NOUVELLE_AFFECTATION);
			break;
		default:
			throw new Impossible("ERREUR !");
		}
	}

	public RemoteInputStream getFile(String fileName) throws Exception {
		// create a RemoteStreamServer (note the finally block which only releases
		// the RMI resources if the method fails before returning.)
		RemoteInputStreamServer istream = null;
		try {
			istream = new GZIPRemoteInputStream(new BufferedInputStream(
					new FileInputStream(DATADIR+"/"+fileName)));
			// export the final stream for returning to the client
			RemoteInputStream result = istream.export();
			// after all the hard work, discard the local reference (we are passing
			// responsibility to the client)
			istream = null;
			return result;
		} finally {
			// we will only close the stream here if the server fails before
			// returning an exported stream
			if(istream != null) istream.close();
		}
	}

	public String imprimerEvalParcours(Parcours p) throws Exception {
		updateDemandes();
		BufferedWriter file=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(DATADIR+"/liste.tex"), "ISO-8859-1"));
		// ordre alphabetique
		SortedSet<Dossier> aux = new TreeSet<Dossier>(new ComparatorDossierParcours());
		for (Dossier dos : dossiers)
			aux.add(dos);
		for (Dossier dos : aux) {
			if(dos.getDemande()!=null) {
				for(Parcours pa: dos.getDemande().getParcours()) {
					if(p.getSigle().contentEquals(pa.getSigle())) {
						dos.imprimerEvaluation(file);
					}
				}	
			}
		}
		file.close();
		String filename = "evaluations.pdf";
		pdflatex(filename);
		return filename;
	}
	
	public String imprimer(Bd.Documents doc) throws Exception {
		String filename="";
		BufferedWriter file=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(DATADIR+"/liste.tex"), "ISO-8859-1"));
		
		switch(doc){
		case PARCOURS:
			filename = "parcours.pdf";
			imprimeParcours(file, Bd.Documents.PARCOURS);
			break;
		case PARCOURS_SYNTHESE:
			filename = "parcours-synthese.pdf";
			imprimeParcours(file, Bd.Documents.PARCOURS_SYNTHESE);
			break;
		case UEs_DETAILS:
			filename = "UEs-detail.pdf";
			imprimerUesDetailsContraintes(file);
			break;
		case UEs_SYNTHESE:
			filename = "UEs.pdf";
			imprimeUes(file);
			break;
		case STATISTIQUES:
			filename = "statistiques.pdf";
			imprimerStatistiques(file);
			break;
		case EVALUATIONS:
			filename = "evaluations.pdf";
			imprimerEvaluations(file);
			break;
		case DEMANDES:
			filename = "demandes.pdf";
			imprimerDemandes(file);
			break;
		case PROPOSITIONS:
			filename = "propositions.pdf";
			imprimerPropositions(file);
			break;
		case COURRIERS:
			filename = "courrier.pdf";
			imprimerCourriers(file, true);
			break;
		case COURRIERS_SUIVANTS:
			filename = "courrier.pdf";
			imprimerCourriers(file, false);
			break;
		default:
			file.close();
			throw new Impossible("ERREUR !");	
		}
		file.close();
		pdflatex(filename);
		return filename;
	}

///////////////////////////////
	private void imprimeParcours(BufferedWriter file, Bd.Documents doc) throws Exception {
		// on recalcule les demandes pour corriger un bug !
		updateDemandes();
		for (Parcours par : parcours)
			if(doc==Bd.Documents.PARCOURS)
				par.imprimer(file);
			else
				par.imprimerSynthese(file);
	}
////////////////////
	private void imprimerUesDetailsContraintes(BufferedWriter file) throws Exception {
		// Toutes les UEs 
		SortedSet<UniteEnseignement> uesAux = new TreeSet<UniteEnseignement>(new ComparatorUE());
		for (UniteEnseignement ue : ues)
			uesAux.add(ue);

		for (UniteEnseignement ue : uesAux) {
			ue.imprimerAvecContraintes(file);
		}
		
		file.flush();
		int[][] uesNb = new int[uesAux.size()][uesAux.size()];
		for (int i=0; i<uesNb.length; i++)
			for (int j=0; j<uesNb[i].length; j++)
				uesNb[i][j] = 0;
		for (UniteEnseignement ue : uesAux) {
			Set<Dossier> prop = ue.getPropositions();
			for (Dossier dos : prop){
				for (UniteEnseignement ue2 : dos.getProposition().getParcours().getUeObligatoires())
					uesNb[ue.getIdent().intValue()][ue2.getIdent().intValue()]++;
				for (UniteEnseignement ue2 : dos.getProposition().getUesChoix1())
					uesNb[ue.getIdent().intValue()][ue2.getIdent().intValue()]++;
				for (UniteEnseignement ue2 : dos.getProposition().getUesChoix2())
					uesNb[ue.getIdent().intValue()][ue2.getIdent().intValue()]++;
			}
		}
		
        file.write("\\newpage\n"+
                "\\subsection*{Contraintes sur les UE}\n"+
                "\\begin{footnotesize}\n"+
                "\\begin{longtable}{l||");
     for (int i=0; i<uesNb.length; i++)
         file.write("c|");
     file.write("}\n\\endhead\n\\hline\n&");
     int k=uesAux.size();
     for (UniteEnseignement ue : uesAux){
    	 if (k>1)
             file.write("\\begin{turn}{90}"+ue.getSigle()+
                        "\\end{turn}&");
         else
             file.write("\\begin{turn}{90}"+ue.getSigle()+
                        "\\end{turn}\\\\\\hline\\hline\n");
         k--;
     }
     int i=0;
     for (UniteEnseignement ue : uesAux) {
         file.write(ue.getSigle()+"&");
         for (int j=0; j<uesNb[i].length; j++){
             if (j<uesNb[i].length-1)
                 if (uesNb[i][j]>0)
                     file.write(uesNb[i][j]+"&");
                 else
                     file.write("&");
             else
                 if (uesNb[i][j]>0)
                     file.write(uesNb[i][j]+"\\\\\\hline\n");
                 else
                     file.write("\\\\\\hline\n");
         }
     i++;
     }
     file.write("\\end{longtable}\n"+
                "\\end{footnotesize}\n");
	}
///////////////////
	private void imprimeUes(BufferedWriter file) throws Exception {
		file.write("\\begin{scriptsize}\n"+
				"\\begin{longtable}{llrllp{5.5cm}r}\n"+
				"\\caption*{Liste des UE}\\\\\n"+
				"\\endhead Intitule&Sigle&Quota&Obligatoire&Choix 1&Choix 2&Propose\\\\\n"+
		"\\endhead\n\\hline\n\\hline\n");
		for (UniteEnseignement ue : ues)
			ue.imprimer(file);
		file.write("\\end{longtable}\n"+
				   "\\end{scriptsize}\n");
	}
	
/////////////////////
	private void imprimerStatistiques(BufferedWriter file) throws Exception {
		updateDemandes();

		// calcul des premiers et des derniers rangs par parcours
		SortedSet<Dossier> aux = new TreeSet<Dossier>(new ComparatorDossierMoyenne());
		for (Dossier dos : dossiers)
			aux.add(dos);
		// premier, dernier et nb desistements par parcours
		int[][] rang = new int[parcours.size()+1][3];
		for (int i=0; i<rang.length; i++)
			for (int j=0; j<rang[i].length; j++)
				rang[i][j] = 0;
		int pos = 1;
		for (Dossier dos : aux) {
			if (dos.getProposition() != null) {
				int par = dos.getProposition().getParcours().getIdent();
				if (rang[par][0] == 0)
					rang[par][0] = pos;
				else
					rang[par][1] = pos;
				if (dos.getReponseCoupon().equals("Retire") || dos.getReponseCoupon().equals("NON +")){
					rang[par][2]++;
					rang[rang.length-1][2]++;
				}
			}
			pos++;
		}
		file.write("\\begin{longtable}{|l||r||*{"+
				parcours.size()+"}{r|}|r|r||r|}\n"+
				"\\caption*{R\\'epartition des retenus "+
				"par choix et par rang}\\\\\n"+
		"Parcours&Total");
		for (int i=0; i<parcours.size(); i++)
			file.write("&"+(i+1));
		file.write("&Premier&Dernier&D\\'esistements\\\\\\hline\\hline\n\\endhead\n");
		int[][] nbc = new int[2][parcours.size()+1];
		for (int i=0; i<nbc.length; i++)
			for (int j=0; j<nbc[i].length; j++)
				nbc[i][j] = 0;
		for (Parcours par : parcours) {
			for (int j=0; j<nbc[1].length; j++)
				nbc[1][j] = 0;
			for (Dossier dos : par.getPropositions()) {
				nbc[1][dos.getProposition().valeur(dos.getDemande())[0]]++;
				nbc[1][0]++;
			}
			file.write(par.getSigle());
			for (int j=0; j<nbc[1].length; j++){
				if (nbc[1][j] != 0)
					file.write("&"+nbc[1][j]);
				else
					file.write("&");
				nbc[0][j] += nbc[1][j];
			}
			file.write("&"+rang[par.getIdent()][0]+"&"+rang[par.getIdent()][1]+
					"&"+rang[par.getIdent()][2]+"\\\\\\hline\n");
		}
		file.write("\\hline");
		for (int j=0; j<nbc[0].length; j++)
			if (nbc[0][j] != 0)
				file.write("&"+nbc[0][j]);
			else
				file.write("&");
		file.write("&&&"+rang[rang.length-1][2]+
		"\\\\\\hline\n");
		file.write("\\end{longtable}\n");
		
		int nb_demandes=0;
		for (Dossier dos : aux) {
			if(dos.getDemande()!=null)
				nb_demandes++;
		}
		
		file.write("\\begin{itemize}");
		file.write("\\item Nombre de candidats ayant déposé au moins 1 v\\oe{}u sur APOFLUX : "+aux.size());

		file.write("\\item Nombre de candidats ayant envoyé un dossier papier : "+nb_demandes);
		file.write("\\item Nombre de candidats ayant reçu une proposition : "+nbc[0][0]);
		file.write("\\end{itemize}");
	}
	
///////////////////////
	private void imprimerEvaluations(BufferedWriter file) throws Exception {
		// ordre alphabetique
		SortedSet<Dossier> aux = new TreeSet<Dossier>(new ComparatorDossierParcours());
		for (Dossier dos : dossiers)
			aux.add(dos);
		for (Dossier dos : aux)
			dos.imprimerEvaluation(file);
	}
/////////////////////
	private void imprimerDemandes(BufferedWriter file) throws Exception {
		// ordre alphabetique
		SortedSet<Dossier> aux = new TreeSet<Dossier>(new ComparatorDossierNom());
		for (Dossier dos : dossiers)
			aux.add(dos);
		file.write("\\begin{scriptsize}\n"+
				"\\begin{longtable}{rlllccrlll}\n"+
				"\\caption*{Liste des demandes}\\\\\n"+
				"Dossier&Nom&Pr\\'enom&"+
				"Origine&Fiche&OK&Note&"+
				"Parcours&Proposition&Coupon"+
		"\\\\\\hline\n\\endhead\n");
		for (Dossier dos : aux)
			dos.imprimerDemande(file);
		file.write("\\end{longtable}\n"+
		"\\end{scriptsize}\n");
	}
//////////////////////////////////////
	private void imprimerPropositions(BufferedWriter file) throws Exception {
		SortedSet<Dossier> aux = new TreeSet<Dossier>(new ComparatorDossierNom());

		// 2010 JS: ajout d'une liste pour les refuses et le motif du refus
		// pour faciliter la saisie apoflux des resultats
		SortedSet<Dossier> refus = new TreeSet<Dossier>(new ComparatorDossierNom());
		for (Dossier dos : dossiers) {
			if (dos.getProposition()!=null)
				aux.add(dos);
			else
				refus.add(dos);
		}
		// ordre alphabetique : les propositions pour affichage
		file.write("\\begin{longtable}{llllll}\n"+
				"\\caption*{Etat des propositions}\\\\\n"+
				"Nom&Pr\\'enom&Apoflux&Parcours&UE choix 1&UE choix 2"+
		"\\\\\\hline\n\\endhead\n");
		for (Dossier dos : aux)
			dos.imprimerPropositionAffichage(file);
		file.write("\\end{longtable}\n");
		file.flush();
		// ordre alphabetique : les propositions en details
		file.write("\\newpage\n"+
				"\\begin{scriptsize}\n"+
				"\\begin{longtable}{lrlllrcllrll}\n"+
				"\\caption*{Etat des propositions}\\\\\n"+
				"Date&&Nom&Pr\\'enom&Origine&&"+
				"Parcours&UE choix 1&UE choix 2&Valeur&R\\'eponse"+
		"\\\\\\hline\n\\endhead\n");
		for (Dossier dos : aux) {
			if(dos.getProposition()!=null)
				dos.getProposition().imprimer(file);
		}
		file.write("\\end{longtable}\n\\end{scriptsize}\n");
		file.flush();
		// ordre des notes : les propositions en details
		aux = new TreeSet<Dossier>(new ComparatorDossierMoyenne());
		for (Dossier dos : dossiers)
			aux.add(dos);
		file.write("\\newpage\n"+
				"\\begin{scriptsize}\n"+
				"\\begin{longtable}{lrlllrcllrll}\n"+
				"\\caption*{Etat des propositions}\\\\\n"+
				"Date&&Nom&Pr\\'enom&Origine&&"+
				"Parcours&UE choix 1&UE choix 2&Valeur&Choix"+
		"\\\\\\hline\n\\endhead\n");
		for (Dossier dos : aux) {
			if(dos.getProposition()!=null)
				dos.getProposition().imprimer(file);
		}
		file.write("\\end{longtable}\n\\end{scriptsize}\n");
		file.flush();

		// ordre alphabetique : les refus et le motif pour affichage
		file.write("\\begin{longtable}{lllp{9cm}p{7cm}}\n"+
				"\\caption*{Etat des refus}\\\\\n"+
				"Nom&Pr\\'enom&Apoflux&Origine&Motif refus"+
		"\\\\\\hline\n\\endhead\n");
		for (Dossier dos : refus)
			dos.imprimerPropositionAffichage(file);
		file.write("\\end{longtable}\n");
	}
	///////////////////////////
	private void imprimerCourriers(BufferedWriter file, boolean premier) throws Exception {
		for (Dossier dos : dossiers)
			dos.imprimerCourrier(file, premier);
	}
	//////////////////////
//	private void imprimerRetenuTexte() throws Exception {
//		SortedSet<Dossier> aux = new TreeSet<Dossier>(new ComparatorDossierNom());
//		for (Dossier dos : dossiers)
//			aux.add(dos);
//		BufferedWriter file=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(DATADIR+"/bibli.txt"), "ISO-8859-1"));
//		for (Dossier dos : aux)
//			dos.imprimerRetenuTexteBibli(file);
//		file.close();
//		SimpleDateFormat df = new SimpleDateFormat("yyyy_MM_dd");
//		file=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(DATADIR+"/retenus_"+df.format(new Date())+".txt"), "ISO-8859-1"));
//		file.write("Numero, Nom, Prenom, Specialite, Liste des UEs\n");
//		for (Dossier dos : aux)
//			dos.imprimerRetenuTexteUes(file);
//		file.close();
//		file=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(DATADIR+"/bordeaux_1.txt"), "ISO-8859-1"));
//
//		for (Dossier dos : aux) {
//			// JS Modif 17/06/08: ajout des specialites dans origine
//			//		if (dos.getOrigine().startsWith("M1 Bdx1"))
//			if (dos.getProposition() == null)
//				file.write(dos.getApoflux()+"; "+
//						dos.getNom()+"; "+
//						dos.getPrenom()+"; "+
//						dos.getNoteDossier()+"; "+
//						dos.getRecevable()+"; ; "+
//						dos.getDemandeListeParcours()+"\n");
//			else if (!dos.getProposition().isRetenu())
//				file.write(dos.getApoflux()+"; "+
//						dos.getNom()+"; "+
//						dos.getPrenom()+"; "+
//						dos.getNoteDossier()+"; "+
//						dos.getRecevable()+"; refuse "+
//						dos.getProposition().getParcours().
//						getSigle()+"; "+
//						dos.getDemandeListeParcours()+"\n");
//		}
//		file.close();
//	}
	
	////////////	
	public void EnvoyerEmails(boolean premier) throws Exception {      
		for (Dossier dos : dossiers) {
			BufferedWriter file=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(DATADIR+"/liste.tex"), "ISO-8859-1"));
			if (dos.imprimerCourrier(file, premier)){
				file.close();
				pdflatex("courrier.pdf");
				String msg = "Bonjour\n"+
				"\nCi-joint un fichier au format pdf décrivant l'avancement de votre "+
				"candidature au Master Informatique de Bordeaux 1."+
				"\n\n\tNuméro de dossier : "+dos.getEtudiant().getIdApoflux()+
				"\n\tNom : "+dos.getEtudiant().getNom()+
				"\n\nVous recevrez d'ici quelques jours le même courrier par voie postale.\n"+
				"\nCordialement,\n"+
				"\nLe secrétariat du Master Informatique,\n\n";
				sendmail(dos.getEtudiant().getEmail(), msg, "Master 2 Informatique Bordeaux 1 : Proposition", DATADIR+"/courrier.pdf");
			}
			else {
				file.close();
			}
		}
	}

	private void sendmail(String email, String msg, String subject, String attach) throws Exception {
		Properties prop = System.getProperties();
		prop.put("mail.smtps.host", "mailhost.labri.fr");

		Session session = Session.getDefaultInstance(prop,null);
		session.setDebug(false);             
		MimeMessage message = new MimeMessage(session);
		message.setFrom(new InternetAddress("secretariat.master@labri.fr"));   
		message.setRecipient(Message.RecipientType.TO,new InternetAddress(email));
		message.setRecipient(Message.RecipientType.BCC,new InternetAddress("bruno.pinaud@labri.fr"));
		message.setRecipient(Message.RecipientType.CC,new InternetAddress("secretariat.master@labri.fr"));
		message.setSubject(subject);

		Multipart mp = new MimeMultipart();
		//première partie du message
		MimeBodyPart mbp1 = new MimeBodyPart();
		mbp1.setText(msg); 
		mp.addBodyPart(mbp1);

		//deuxième partie du message
		if(!attach.isEmpty()) {
			MimeBodyPart mbp2 = new MimeBodyPart();
			mbp2.attachFile(attach);
			mp.addBodyPart(mbp2);
		}

		// add the Multipart to the message
		message.setContent(mp);

		message.setSentDate(new Date());

		SMTPTransport t = (SMTPTransport)session.getTransport("smtps");
		t.connect();
		t.sendMessage(message, message.getAllRecipients());
	}

	// sauvegarde et restauration
	public void Sauvegarde() throws Exception {
		// une sauvegarde datee
		SimpleDateFormat df = new SimpleDateFormat("yyyy_MM_dd_'a'_HH_mm_ss");
		Path save = FileSystems.getDefault().getPath(BACKUPDIR, "bd_"+df.format(new Date())+".back");
		//String file = BACKUPDIR+"bd_"+df.format(new Date())+".back";
		Sauvegarde(save);
		// la sauvegarde qui sera restauree par la comande "restaure"
		//Sauvegarde(BACKUPDIR+DEFAULTBACKUPFILE);
		Path backup = FileSystems.getDefault().getPath(BACKUPDIR, DEFAULTBACKUPFILE);
		Files.copy(save, backup, REPLACE_EXISTING);

	}

	private void Sauvegarde(Path file) throws Exception {
		FileOutputStream ostream = new FileOutputStream(file.toString());
		ObjectOutputStream p = new ObjectOutputStream(ostream);
		p.writeObject(this);
		p.writeInt(DossierImpl.getNumero());
		p.writeInt(ParcoursImpl.getNumero());
		p.writeInt(UniteEnseignementImpl.getNumero());
		p.flush();
		ostream.close();
	}

	public void Clear() throws Exception {
		for (Parcours par : parcours) {
			par.clearDemandes();
			par.clearPropositions();
		}
		for (UniteEnseignement ue : ues){
			ue.clearDemandes();
			ue.clearPropositions();
		}
		dossiers.clear();
		DossierImpl.setNumero(1);
	}

	static public Bd Restaure() throws Exception {
		String file = BACKUPDIR+DEFAULTBACKUPFILE;
		return BdImpl.Restaure(file);
	}

	static public Bd Restaure(String file) throws Exception {
		ObjectInputStream p = new ObjectInputStream(new FileInputStream(file));
		Bd bd = (BdImpl)p.readObject();
		int dosMax = p.readInt();
		int parMax = p.readInt();
		int ueMax = p.readInt();
		DossierImpl.setNumero(dosMax);
		ParcoursImpl.setNumero(parMax);
		UniteEnseignementImpl.setNumero(ueMax);
		p.close();
		return bd;
	}

	private void pdflatex(String target) throws Exception  {
		try {
			Runtime.getRuntime().exec("make -C "+DATADIR+" -s "+target).waitFor();
		}
		catch(Exception ioe){
			ioe.printStackTrace();
		}
	}

	// les methodes pour corriger des bugs !
	private void updateDemandes() throws Exception {
		// nettoyage des anciennes demandes
		for (Parcours par : parcours)
			par.clearDemandes();
		for (UniteEnseignement ue : ues)
			ue.clearDemandes();
		// construction des nouvelles
		for (Dossier dos : dossiers) {
			Demande demande = dos.getDemande();
			if (demande != null)
				for (Fiche fic : demande.getFiches())
					fic.setLiensParUes(true);
		}
	}
}
